/*
 * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Unlicense OR CC0-1.0
 */
/* Includes */
/* STD APIs */
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "parameters.h"

/* ESP APIs */
#include "esp_log.h"

/* FreeRTOS APIs */
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

/* NimBLE stack APIs */
#include "host/ble_hs.h"
#include "host/ble_uuid.h"
#include "host/util/util.h"
#include "nimble/ble.h"

/* NimBLE GAP APIs */
#include "host/ble_gap.h"
#include "services/gap/ble_svc_gap.h"

#include "gap.h"
#include "gatt_svc.h"

static const char *TAG = __FILE_NAME__;

/* Private function declarations */
inline static void format_addr(char *addr_str, uint8_t addr[]);
static void print_conn_desc(struct ble_gap_conn_desc *desc);
static void start_advertising(void);
static int gap_event_handler(struct ble_gap_event *event, void *arg);

/* Private variables */
static uint8_t own_addr_type;

inline static void format_addr(char *addr_str, uint8_t addr[])
{
    sprintf(addr_str, "%02X:%02X:%02X:%02X:%02X:%02X", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
}

static void print_conn_desc(struct ble_gap_conn_desc *desc)
{
    /* Local variables */
    char addr_str[18] = {0};

    /* Connection handle */
    ESP_LOGI(TAG, "connection handle: %d", desc->conn_handle);

    /* Local ID address */
    format_addr(addr_str, desc->our_id_addr.val);
    ESP_LOGI(TAG, "device id address: type=%d, value=%s", desc->our_id_addr.type, addr_str);

    /* Peer ID address */
    format_addr(addr_str, desc->peer_id_addr.val);
    ESP_LOGI(TAG, "peer id address: type=%d, value=%s", desc->peer_id_addr.type, addr_str);

    /* Connection info */
    ESP_LOGI(TAG, "conn_itvl=%d, conn_latency=%d, supervision_timeout=%d, " "encrypted=%d, authenticated=%d, bonded=%d\n",
        desc->conn_itvl, desc->conn_latency, desc->supervision_timeout,
        desc->sec_state.encrypted, desc->sec_state.authenticated, desc->sec_state.bonded);
}

static void start_advertising(void)
{
    /* Local variables */
    const char *name;
    const ble_uuid16_t uuids16 = BLE_UUID16_INIT(0x00FC);
    ParametersType *ptypeParameters = ptypeParametersInfoGet();
    struct ble_hs_adv_fields adv_fields = {0};
    struct ble_hs_adv_fields rsp_fields = {0};
    struct ble_gap_adv_params adv_params = {0};
    uint8_t manufacturer_datas[7];
    int rc = 0;

    /* Set advertising flags */
    adv_fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;

    /* Set device name */
    name = ble_svc_gap_device_name();
    adv_fields.name = (uint8_t *)name;
    adv_fields.name_len = strlen(name);
    adv_fields.name_is_complete = 1;

    /*** 0x02,0x03 - 16-bit service class UUIDs. */
    adv_fields.uuids16 = &uuids16;
    adv_fields.num_uuids16 = 1;
    adv_fields.uuids16_is_complete = 1;

    /* Set advertiement fields */
    if ((rc = ble_gap_adv_set_fields(&adv_fields)) != 0)
    {
        ESP_LOGE(TAG, "failed to set advertising data, error code: %d", rc);
        return;
    }

    /*** 0x16 - Service data - 16-bit UUID. */
    rsp_fields.svc_data_uuid16 = (const uint8_t *)ptypeParameters->PID;
    rsp_fields.svc_data_uuid16_len = strlen(ptypeParameters->PID);

    /*** 0xff - Manufacturer specific data. */
    manufacturer_datas[0] = 4;
    manufacturer_datas[1] = ptypeParameters->networkState;
    rsp_fields.mfg_data = manufacturer_datas;
    rsp_fields.mfg_data_len = 2;

    /* Set scan response fields */
    if ((rc = ble_gap_adv_rsp_set_fields(&rsp_fields)) != 0)
    {
        ESP_LOGE(TAG, "failed to set scan response data, error code: %d", rc);
        return;
    }

    /* 非定向广播 */
    adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
    adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;

    /* Set advertising interval */
    adv_params.itvl_min = BLE_GAP_ADV_ITVL_MS(500);
    adv_params.itvl_max = BLE_GAP_ADV_ITVL_MS(510);

    adv_params.filter_policy = BLE_HCI_ADV_FILT_NONE;

    /* Start advertising */
    if ((rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER, &adv_params, gap_event_handler, NULL)) != 0)
    {
        ESP_LOGE(TAG, "failed to start advertising, error code: %d", rc);
        return;
    }
    ESP_LOGI(TAG, "advertising started!");
}

/*
 * NimBLE applies an event-driven model to keep GAP service going
 * gap_event_handler is a callback function registered when calling
 * ble_gap_adv_start API and called when a GAP event arrives
 */
static int gap_event_handler(struct ble_gap_event *event, void *arg)
{
    ParametersType *ptypeParameters = ptypeParametersInfoGet();
    struct ble_gap_conn_desc desc;
    int rc;

    /* Handle different GAP event */
    switch (event->type)
    {
        /* Connect event */
        case BLE_GAP_EVENT_CONNECT:
            /* A new connection was established or a connection attempt failed. */
            ESP_LOGI(TAG, "connection %s; status=%d", event->connect.status == 0 ? "established" : "failed", event->connect.status);
            ptypeParameters->networkState |= BIT3;

            /* Connection succeeded */
            if (event->connect.status == 0)
            {
                /* Check connection handle */
                if ((rc = ble_gap_conn_find(event->connect.conn_handle, &desc)) != 0)
                {
                    ESP_LOGE(TAG, "failed to find connection by handle, error code: %d", rc);
                    return rc;
                }

                /* Print connection descriptor */
                print_conn_desc(&desc);

                /* Try to update connection parameters */
                struct ble_gap_upd_params params = {.itvl_min = desc.conn_itvl,
                                                    .itvl_max = desc.conn_itvl,
                                                    .latency = 3,
                                                    .supervision_timeout = desc.supervision_timeout};

                if ((rc = ble_gap_update_params(event->connect.conn_handle, &params)) != 0)
                {
                    ESP_LOGE( TAG, "failed to update connection parameters, error code: %d", rc);
                    return rc;
                }
            }
            /* Connection failed, restart advertising */
            else
            {
                start_advertising();
            }
            return 0;

        /* Disconnect event */
        case BLE_GAP_EVENT_DISCONNECT:
            /* A connection was terminated, print connection descriptor */
            ESP_LOGI(TAG, "disconnected from peer; reason=%d", event->disconnect.reason);
            ptypeParameters->networkState &= ~BIT3;

            /* Restart advertising */
            start_advertising();
            return 0;

        /* Connection parameters update event */
        case BLE_GAP_EVENT_CONN_UPDATE:
            /* The central has updated the connection parameters. */
            ESP_LOGI(TAG, "connection updated; status=%d", event->conn_update.status);

            /* Print connection descriptor */
            if ((rc = ble_gap_conn_find(event->conn_update.conn_handle, &desc)) != 0)
            {
                ESP_LOGE(TAG, "failed to find connection by handle, error code: %d", rc);
                return rc;
            }
            print_conn_desc(&desc);
            return rc;

        /* Advertising complete event */
        case BLE_GAP_EVENT_ADV_COMPLETE:
            /* Advertising completed, restart advertising */
            ESP_LOGI(TAG, "advertise complete; reason=%d", event->adv_complete.reason);
            start_advertising();
            return 0;

        /* Notification sent event */
        case BLE_GAP_EVENT_NOTIFY_TX:
            if ((event->notify_tx.status != 0) && (event->notify_tx.status != BLE_HS_EDONE))
            {
                /* Print notification info on error */
                ESP_LOGI(TAG, "notify event; conn_handle=%d attr_handle=%d " "status=%d is_indication=%d",
                        event->notify_tx.conn_handle, event->notify_tx.attr_handle, event->notify_tx.status, event->notify_tx.indication);
            }
            return 0;

        /* Subscribe event */
        case BLE_GAP_EVENT_SUBSCRIBE:
            /* Print subscription info to log */
            ESP_LOGI(TAG, "subscribe event; conn_handle=%d attr_handle=%d " "reason=%d prevn=%d curn=%d previ=%d curi=%d",
                    event->subscribe.conn_handle, event->subscribe.attr_handle, event->subscribe.reason, event->subscribe.prev_notify,
                    event->subscribe.cur_notify, event->subscribe.prev_indicate, event->subscribe.cur_indicate);

            /* GATT subscribe event callback */
            gatt_svr_subscribe_cb(event);
            return 0;

        /* MTU update event */
        case BLE_GAP_EVENT_MTU:
            /* Print MTU update info to log */
            ESP_LOGI(TAG, "mtu update event; conn_handle=%d cid=%d mtu=%d", event->mtu.conn_handle, event->mtu.channel_id, event->mtu.value);
            return 0;
    }

    return 0;
}


/* Public functions */
void adv_init(void)
{
    /* Local variables */
    int rc;
    uint8_t addr_val[6] = {0};
    char addr_str[18] = {0};

    /* Make sure we have proper BT identity address set (random preferred) */
    if ((rc = ble_hs_util_ensure_addr(0)) != 0)
    {
        ESP_LOGE(TAG, "device does not have any available bt address!");
        return;
    }

    /* Figure out BT address to use while advertising (no privacy for now) */
    if ((rc = ble_hs_id_infer_auto(0, &own_addr_type)) != 0)
    {
        ESP_LOGE(TAG, "failed to infer address type, error code: %d", rc);
        return;
    }

    /* Printing ADDR */
    if ((rc = ble_hs_id_copy_addr(own_addr_type, addr_val, NULL)) != 0)
    {
        ESP_LOGE(TAG, "failed to copy device address, error code: %d", rc);
        return;
    }

    format_addr(addr_str, addr_val);
    ESP_LOGI(TAG, "device address: %s", addr_str);

    /* Start advertising. */
    start_advertising();
}

int gap_init(void)
{
    ParametersType *ptypeParameters = ptypeParametersInfoGet();
    int rc;

    /* Call NimBLE GAP initialization API */
    ble_svc_gap_init();

    /* Set GAP device name */
    if ((rc = ble_svc_gap_device_name_set(ptypeParameters->BT_NAME)) != 0)
    {
        ESP_LOGE(TAG, "failed to set device name to %s, error code: %d", ptypeParameters->BT_NAME, rc);
    }

    return rc;
}
